package com.copy.proxy.create;

import com.copy.exception.FliedMappingException;
import com.copy.mapping.PropertyMappingHandler;
import com.copy.mark.CloneType;
import com.copy.parse.transform.TypeTransform;
import com.copy.pojo.BeanMetaData;
import com.copy.pojo.BeanMethod;
import com.copy.pojo.OperatorIndex;
import com.copy.pojo.Property;
import com.copy.scan.ScanBasePackageStrategy;
import com.copy.scan.other.ScanBasePackageByOtherHandler;
import com.copy.util.StackExpression;
import org.junit.runner.Runner;

import javax.management.relation.RoleUnresolved;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class GenericCreatePorxyHandler extends PropertyMappingHandler implements CreateProxyStrategy, InvocationHandler {
    private static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
    private String[]  operatorChar = {"+","-","*","/","%"};
    private String[]  operatorChar2 = {"+","-","*","/","%","(",")"};
    private String[] numberChar ={"0","1","2","3","4","5","6","7","8","9"};
    //缓存类型转换器，实例。
    protected Map<Class<TypeTransform>,TypeTransform> typeTransformMap=new ConcurrentHashMap<>();
    //JVM保证深拷贝的数据结构
    protected Class[] defaultType = {Integer.class,String.class,Long.class,Byte.class,Short.class,Float.class,Double.class,Boolean.class,Character.class};

    protected GenericCreatePorxyHandler(String basePackage) {
        super(null);
        this.basePackage=basePackage;
    }
    protected GenericCreatePorxyHandler(ScanBasePackageStrategy scanBasePackageStrategy,String basePackage){
        super(scanBasePackageStrategy);
        this.basePackage=basePackage;
        refresh();
    }
    public static class Builder{
        private static GenericCreatePorxyHandler genericCreatePorxyHandler = new GenericCreatePorxyHandler(new ScanBasePackageByOtherHandler(),"com");
    }
    public static GenericCreatePorxyHandler getInstance(){
        return Builder.genericCreatePorxyHandler;
    }

    @Override
    public <T> T createProxyBean(Class<T> cls, BeanMetaData beanMetaData)throws Exception {
        T t= (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, this);
         return t;
    }
    public <T> T getTypeValue(Class<T> cls,Object value)throws Exception{
        return (T)value;
    }
    public boolean isDoubleOrFloat(String s)throws Exception{
        try {
            Double.parseDouble(s);
        }catch (Exception e){
            return false;
        }
        return true;
    }
    public Object doAutoCast(Class targetClass,Object value)throws Exception{
        if (targetClass==value.getClass()){//短路操作
            return  value;
        }
        Class<?> temClass = value.getClass();
        if (targetClass==Integer.class){
            if (temClass==Short.class||temClass==Byte.class){
                return Integer.parseInt(value+"");
            }
        }
        if (targetClass==Double.class){
            if (temClass==Integer.class||temClass==Float.class||temClass==Long.class||temClass==Short.class||temClass==Byte.class){
                return Double.parseDouble(value+"");
            }
        }
        if (targetClass==Float.class){
            if (temClass==Integer.class||temClass==Long.class||temClass==Short.class||temClass==Byte.class){
                return Float.parseFloat(value+"");
            }
        }
        if (targetClass==Long.class){
            if (temClass==Integer.class||temClass==Short.class||temClass==Byte.class){
                return Long.parseLong(value+"");
            }
        }

        if (targetClass==Short.class){
            if (temClass==Byte.class){
                return Short.parseShort(value+"");
            }
        }
        return  value;
    }
    public Object doForceCast(Class targetClass,Object value) throws Exception {
            if (targetClass==value.getClass()){//短路操作
                return  value;
            }
            Class cls = value.getClass();
            String tem=value.toString();
            int indexOf = tem.indexOf(".");
            if (targetClass==Integer.class){
                if (cls==BigDecimal.class||cls==Long.class||cls==Double.class||cls==Float.class||cls==String.class ){
                    if (cls==String.class){
                        if (!isDoubleOrFloat(tem)){
                            return null;
                        }
                    }
                    if (indexOf!=-1){
                        return  Integer.parseInt(tem.substring(0,indexOf));
                    }else{
                        return  Integer.parseInt(tem);
                    }
                }
            }
            if (targetClass==Long.class){
                if (cls==BigDecimal.class||cls==Double.class||cls==Float.class||cls==String.class){
                    if (cls==String.class){
                        if (!isDoubleOrFloat(tem)){
                            return null;
                        }
                    }
                    if (indexOf!=-1){
                        return  Long.parseLong(tem.substring(0,indexOf));
                    }else{
                        return Long.parseLong(tem);
                    }
                }
            }
            if (targetClass==Float.class){
                if (cls==BigDecimal.class||cls==Double.class||cls==String.class){
                    return Float.parseFloat(tem);
                }
            }
            if (targetClass==Double.class){
                if (cls==BigDecimal.class||cls==String.class){
                    return Double.parseDouble(tem);
                }
            }
            if (targetClass==Byte.class){
                if (cls==BigDecimal.class||cls==Short.class||cls==Integer.class||cls==Long.class||cls==Double.class||cls==Float.class||cls==String.class) {
                    if (cls==String.class){
                        if (!isDoubleOrFloat(tem)){
                            return null;
                        }
                    }
                    if (indexOf!=-1){
                        return  Byte.parseByte(tem.substring(0,indexOf));
                    }else{
                        return Byte.parseByte(tem);
                    }
                }
            }
            if (targetClass==Short.class){
                if (cls==BigDecimal.class||cls==Integer.class||cls==Long.class||cls==Double.class||cls==Float.class||cls==String.class) {
                    if (cls==String.class){
                        if (!isDoubleOrFloat(tem)){
                            return null;
                        }
                    }
                    if (indexOf!=-1){
                        return  Short.parseShort(tem.substring(0,indexOf));
                    }else{
                        return Short.parseShort(tem);
                    }
                }
            }
            if (targetClass==String.class){
                return tem;
            }
            if (targetClass==BigDecimal.class){
                return new BigDecimal(tem);
            }
            return value;
    }
    public void cloneProperty(Object targetBean, Field targetFiled, Object value, CloneType cloneType,boolean openForceCast) throws Exception {
        //基本数据类型+String，JVM保证深拷贝
        for (int i = 0; i < defaultType.length; i++) {
            if (targetFiled.getType()==defaultType[i]){
                    Class cls = targetFiled.getType();
                    value=doAutoCast(cls,value);
                    if (openForceCast){
                        value = doForceCast(cls,value);
                    }
                    targetFiled.set(targetBean, value);
                return;
            }
        }
        //其他数据类型，不能保证深拷贝.
        //注意事项：被深拷贝的对象，必须实现Serializable序列化接口
        if (cloneType==CloneType.DeepClone){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(value);
                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bis);
                value = ois.readObject();

            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

        }
        //设置属性
        try {
            if (openForceCast){
                value = doForceCast(targetFiled.getType(),value);
            }
            targetFiled.set(targetBean, value);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object transform(Class<TypeTransform> transformClass, Object oldValue)throws Exception {
        if (transformClass!=null){
            //尝试从缓存中获取
            TypeTransform typeTransform = typeTransformMap.get(transformClass);
            if (typeTransform!=null){
                return  typeTransform.transform(oldValue);
            }else{
                try {
                    typeTransform = transformClass.newInstance();
                    //putIfAbsent保证线程安全
                    typeTransformMap.putIfAbsent(transformClass,typeTransform);
                    return typeTransform.transform(oldValue);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                return oldValue;
            }
        }else{
            return  oldValue;
        }
    }

    @Override
    public <T> T getProxyBean(Class<T> cls) {
        return (T)proxyBeans.get(cls);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        try {
            Class<?>[] interfaces = proxy.getClass().getInterfaces();
            BeanMetaData beanMetaData = beanMetaDataMap.get(interfaces[0]);
            Set<Method> methods = beanMetaData.getBeanMethods().keySet();
            //安全短路，防止恶意调用System.out.println(proxyBean); --》 toString
            if (!methods.contains(method)){
                return method.getName()+"(): 该方法没有代理！请勿调用！";
            }
            BeanMethod beanMethod =beanMetaData .getBeanMethod(method);
            if (beanMethod.isClone()){
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(bos);
                    oos.writeObject(args[0]);
                    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                    ObjectInputStream ois = new ObjectInputStream(bis);
                    return ois.readObject();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

            Object targetBean = null;
            Class targetClass = method.getReturnType();//目标类型
            //匹配字段
            Map<String, Property> propertyMapping = beanMethod.getPropertyMapping();
            //开启匹配
            Iterator<Map.Entry<String, Property>> entrys = propertyMapping.entrySet().iterator();
            while (entrys.hasNext()) {
                Map.Entry<String, Property> entry = entrys.next();
                Property property = entry.getValue();
                String targetPropertyName = property.getTargetPropertyName();
                String resourcePropertyName = property.getResorcePropertyName();
                //类型转换器
                Class<TypeTransform> typeTransformClass = property.getTypeTransform();
                Field targetFiled=null;
                Object oldValue=null;
                if (beanMethod.isOpenParam()){
                    oldValue = resolverBigDecimalOperator( method, args, resourcePropertyName);
                    //arg0.xxx
                    if (beanMethod.isEagerInstance()){
                        //arg0.Xxx
                        targetFiled = getNextField(targetClass,targetPropertyName.substring(5));//递归目标字段
                        targetFiled.setAccessible(true);
                        String[] split = targetPropertyName.split("\\.");
                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            String paramName = parameters[i].getName();
                            if (paramName.equals(split[0])){
                                targetBean = args[i];
                            }
                        }
                        if (targetBean==null){
                            throw new FliedMappingException(method.getClass(),targetPropertyName);
                        }
                    }else {//xxx
                        targetFiled = getNextField(targetClass,targetPropertyName);//递归目标字段
                        targetFiled.setAccessible(true);
                        targetBean = targetClass.newInstance();
                    }
                }else{
                    //xxx
                    targetFiled = getNextField(targetClass,targetPropertyName);//递归目标字段
                    targetFiled.setAccessible(true);
                    if (beanMethod.isEagerInstance()){
                        targetBean = args[0];
                        Field declaredField = args[1].getClass().getDeclaredField(resourcePropertyName);
                        declaredField.setAccessible(true);
                        oldValue = declaredField.get(args[1]);
                    }else{
                        targetBean = targetClass.newInstance();
                        Field declaredField = args[0].getClass().getDeclaredField(resourcePropertyName);
                        declaredField.setAccessible(true);
                        oldValue = declaredField.get(args[0]);
                    }
                }
                if (oldValue==null){
                    throw new FliedMappingException(proxy.getClass().getInterfaces()[0],resourcePropertyName);
                }
                oldValue = transform(typeTransformClass,oldValue);
                if (oldValue==null){
                    throw new RuntimeException("GenericCreateProxyHandler:215行,类型转换器:"+typeTransformClass+",返回结果为null");
                }
                //设置数据
                try {
                    cloneProperty(targetBean,targetFiled,oldValue,property.getCloneType(),property.isOpenForceCast());
                }catch (Exception e){
                    throw new RuntimeException("属性:"+resourcePropertyName+",类型"+oldValue.getClass()+"：不能转换目标类型"+targetFiled.getType());
                }
            }
            return targetBean;
        }catch (Exception e){
            e.printStackTrace();
        }catch (Throwable e1){
            e1.printStackTrace();
        }
        return null;
    }
    public Field getNextField(Class cls,String propertyName) throws Exception {
        //propertyName = propertyName.substring(5);
        if (propertyName.contains(".")){
            String[] split = propertyName.split("\\.");
            cls = cls.getDeclaredField(split[0]).getType();
            propertyName=propertyName.substring(propertyName.indexOf(".")+1);
            return getNextField(cls,propertyName);
        }
        try {
            return cls.getDeclaredField(propertyName);
        }catch (Exception e){
            throw new FliedMappingException(cls,propertyName);
        }

    }
    public boolean isOperatorChar(char c){
        for (int i = 0; i < operatorChar2.length; i++) {
            if (operatorChar2[i].equals(c+"")){
                return true;
            }
        }
        return false;
    }
    //(1+2)*3
    public Object resolverBigDecimalOperator(Method method,Object[] args,String str) throws Exception {
        char[] chars = str.toCharArray();
        List<String> s = new ArrayList<>();
        String name="";
        boolean isOperator = false;
        boolean isString = false;
        for (int i = 0; i < chars.length; i++) {
            if(isOperatorChar(chars[i])&&!isString){
/*                if (name.substring(0,1).equals("'")&&name.substring(name.length()-1).equals("'")){

                }*/
                isOperator = true;
                if (!name.equals("")){
                    Object tem=null;
                    if (name.contains("'")){
                        s.add(name.substring(1,name.length()-1));
                    }else{
                        tem= getNextValue(method, args, name);
                        if (tem instanceof BigDecimal){
                            s.add(((BigDecimal)tem).toString());
                        }else{
                            s.add(""+tem);
                        }
                    }
                }
                s.add(chars[i]+"");
                name="";
            }else{
                if ("'".equals(chars[i]+"")){
                    if (!isString){
                        isString=true;
                    }else{
                        isString=false;
                    }
                }
                name+=chars[i];
                if (i==chars.length-1){
                    if (name.contains("'")){
                        s.add(name.substring(1,name.length()-1));
                    }else{
                        Object tem = getNextValue(method, args, name);
                        if (!isOperator){
                            return tem;
                        }
                        if (tem instanceof BigDecimal){
                            s.add(((BigDecimal)tem).toString());
                        }else{
                            s.add(tem+"");
                        }
                    }
                }
            }
        }

        StackExpression stack = new StackExpression();
        String result = stack.getResult(s);
        try {
            return Integer.parseInt(result);//还原真实类型，整形直接量int
        }catch (Exception e){
            try {
                return Double.parseDouble(result);//还原真实类型，浮点型直接量double
            }catch (Exception e1){
                return result;
            }
        }
    }

    public boolean isNumber(String firstChar){
        for (int k = 0; k < numberChar.length; k++) {
            if (firstChar.equals(numberChar[k])){
                return true;
            }
        }
        return false;
    }
    public Object getNextValue(Method method,Object[] args,String propertyName) throws Exception {
            if (isNumber(propertyName.substring(0,1))){
                return propertyName;
            }
            String pName=propertyName;
            Object methodBean=null;
            String[] split = pName.split("\\.");
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                String paramName = parameters[i].getName();
                if (paramName.equals(split[0])){
                    methodBean = args[i];
                }
            }
            if (methodBean==null){
                throw new FliedMappingException(method.getClass(),propertyName);
            }
            Object value=null;
            if (pName.contains(".")){
                value=getNextValueByObject(methodBean,pName.substring(pName.indexOf(".")+1));
                if (value == null){
                    throw new FliedMappingException(method.getClass(),pName.substring(pName.indexOf(".")+1));
                }
            }else{
                value=methodBean;
                if (value == null){
                    throw new FliedMappingException(method.getClass(),pName.substring(pName.indexOf(".")+1));
                }
            }
            return  value;

    }
    //name
    public Object getNextValueByObject(Object obj,String propertyName) throws Exception {
        if (propertyName.contains(".")){
            String[] split = propertyName.split("\\.");
            Field declaredField=null;
            try {
                declaredField = obj.getClass().getDeclaredField(split[0]);
            }catch (Exception e){
                throw new FliedMappingException(obj.getClass(),propertyName);
            }

            declaredField.setAccessible(true);
            obj =declaredField .get(obj);
            propertyName=propertyName.substring(propertyName.indexOf(".")+1);
            return getNextValueByObject(obj,propertyName);
        }
        Field declaredField;
        try {
            declaredField= obj.getClass().getDeclaredField(propertyName);
        }catch (Exception e){
            throw new FliedMappingException(obj.getClass(),propertyName);
        }

        declaredField.setAccessible(true);
        return declaredField.get(obj);
    }

}
